package org.uca.dss.curso1011.grupo3.gestionreservas;

import com.db4o.query.Predicate;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.uca.dss.curso1011.grupo3.basededatos.DBUtils;
import org.uca.dss.curso1011.grupo3.interfaz.InterfazCompras;

/**
 * Clase encargada de llevar la gestion de todas las reservas del sistema.
 * @author Adolfo Barroso, Levi Orta, Juan Manuel Chaves
 */
public class GestionReservas implements InterfazCompras {

    //Atributos de la clase
    protected  ListadoTrenes trenes;
    protected ListadoTrayectos trayectos;

    /**
     *
     * Constructor de la clase GestionReservas. Para poder realizar una gestion
     * de reservar es necesario pasar los strings con los paths donde se encuentran
     * los ficheros donde estan guardados los datos de todos los trenes y trayectos
     * que forman parte del sistema. Si alguno de estos dos ficheros no se encuentra
     * se lanzara la correspondiente exepcion.
     *
     * @param ficheroTrenes: fichero donde se encuentra almacenado la informacion de los trenes
     * @param ficheroTrayecto: fichero donde se encuentra almacenado la informacion de los trayectos
     */
    public GestionReservas(String ficheroTrenes, String ficheroTrayecto) throws FileNotFoundException, IOException {
        trenes = new ListadoTrenes();
        try {
            trenes.loadCSV(ficheroTrenes);
        } catch (FileNotFoundException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        }

        trayectos = new ListadoTrayectos(trenes);
        try {
            trayectos.loadCSV(ficheroTrayecto);
        } catch (FileNotFoundException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        }
    }

    /**
     * Devuelve la lista con todas las reservas almacenadas por el sistema y
     * y guardadas en la base de datos.
     *
     * @return una lista con las reservas alacenadas por el sistema
     */
    public List<Reserva> getReservas() {
        List<Reserva> reservas = DBUtils.getDb().query(new Predicate<Reserva>() {

            public boolean match(Reserva r) {
                return true;
            }
        });

        return reservas;
    }

    /**
     *
     * Devuelve el numero de reservas que existen almacenadas en el sistema para
     * un determinado trayecto y para una determinada hora de salida, sin tener
     * en cuenta el día del viaje.
     *
     * @param trayecto, trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @param hora, hora del trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @return numero de reservas efectuadas.
     */
    private int numReservasTrayecto(Trayecto trayecto, LocalTime hora) {
        int cont = 0;
        //Cargamos todas las reservas
        List<Reserva> reservas = this.getReservas();

        //Por cada una de las reservas
        for (int i = 0; i < reservas.size(); i++) {
            //Comprobamos que corresponda con el trayecto dado
            if (reservas.get(i).getTrayecto().equals(trayecto)
                    && reservas.get(i).getHoraSalida().equals(hora)) {
                cont++;
            }
        }
        return cont;
    }

    /**
     * Devuelve el numero de reservas que almacena el sistema de un determinado
     * trayecto, para un determinado día, y para una determinada hora de salida.
     *
     * @param trayecto, trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @param fecha, fecha del trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @param hora, hora del trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @return numero de reservas efectuadas.
     */
    private int numReservasTrayecto(Trayecto trayecto, LocalDate fecha, LocalTime hora) {
        int cont = 0;
        //Cargamos todas las reservas
        List<Reserva> reservas = this.getReservas();

        // Recorremos cada una de las reservas
        for (int i = 0; i < reservas.size(); i++) {
            //Comprobamos que la reserva corresponda con el trayecto, fecha y hora dados
            if (reservas.get(i).getTrayecto().equals(trayecto)
                    && reservas.get(i).getFechaViaje().equals(fecha)
                    && reservas.get(i).getHoraSalida().equals(hora)) {
                cont++;
            }
        }
        return cont;
    }

    /**
     * Devuelve una lista con las reservas del trayecto dado en una fecha concreta
     * y para una hora determinada.
     *
     * @param trayecto, trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @param fecha, fecha del trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @param hora, hora del trayecto para el cual se quiere conocer el numero de reservas efectuadas
     * @return Lista de las reservas para un trayecto dado, a una hora concreta y para una determinada fecha.
     */
    public List<Reserva> listaReservasTrayecto(Trayecto trayecto, LocalDate fecha, LocalTime hora) {
        int cont = 0;
        //Cargamos todas las reservas
        List<Reserva> todaReservas = this.getReservas();
        List<Reserva> reservas = new ArrayList<Reserva>();

        // Recorremos cada una de las reservas
        for (int i = 0; i < todaReservas.size(); i++) {
            //Comprobamos que la reserva corresponda con el trayecto, fecha y hora dados
            if (todaReservas.get(i).getTrayecto().equals(trayecto)
                    && todaReservas.get(i).getFechaViaje().equals(fecha)
                    && todaReservas.get(i).getHoraSalida().equals(hora)) {
                reservas.add(todaReservas.get(i));
            }
        }
        return reservas;
    }

    /**
     * Permite realizar la reserva de un asiento para un viaje, dada su ciudad
     * de origen, su ciudad destino, la fecha del viaje, la hora de salida, y el
     * identificador de la reserva. En el caso de que alguno de los parametros no sea correcto,
     * debe de devolver una excepción IllegalArgumentException
     *
     * @param origen ciudad de origen
     * @param destino ciudad de destino
     * @param fecha fecha del viaje
     * @param hora hora del viaje
     * @return cadena que identifica a la reserva. Puede ser cualquier cadena
     *
     */
    @Override
    public String reservaAsiento(String origen, String destino, LocalDate fecha, LocalTime hora) {

        List<Reserva> reservas = this.getReservas();

        // variable donde almacenamos el maximo de los identificadores de las reservas efectuadas
        int maxIdReserva = 0;

        // calculamos el maximo
        if (!reservas.isEmpty()) {
            for (int i = 0; i < reservas.size(); i++) {
                if (Integer.parseInt(reservas.get(i).getIdReserva()) > maxIdReserva) {
                    maxIdReserva = Integer.parseInt(reservas.get(i).getIdReserva());
                }
            }
        }

        maxIdReserva++;

        try {
            Trayecto trayecto = this.trayectos.getTrayecto(origen, destino, hora);

            //Comprobamos que no este el tren lleno
            if (numReservasTrayecto(trayecto, hora) < trayecto.getTren().getNAsientos()) {
                Reserva res = new Reserva(String.valueOf(maxIdReserva), fecha, trayecto, hora);
                DBUtils.getDb().store(res);
                return res.getIdReserva();
            } else {
                throw new RuntimeException("El tren esta completo");
            }
        } catch (IllegalArgumentException ex) {
            throw ex;
        }
    }

    /**
     * Permite realizar la reserva de un asiento para un viaje, dada su ciudad
     * de origen, su ciudad destino, la fecha del viaje, la hora de salida, y el
     * identificador de la reserva. En el caso de que alguno de los parametros no sea correcto,
     * debe de devolver una excepción IllegalArgumentException
     *
     * @param origen ciudad de origen
     * @param destino ciudad de destino
     * @param fecha fecha del viaje
     * @param hora hora del viaje
     * @param numAsiento numero de asiento a reservar
     * @return cadena que identifica a la reserva. Puede ser cualquier cadena
     *
     */
    public String reservaAsiento(String origen, String destino, LocalDate fecha, LocalTime hora, int numAsiento) {

        List<Reserva> reservas = this.getReservas();

        // variable donde almacenamos el maximo de los identificadores de las reservas efectuadas
        int maxIdReserva = 0;

        // calculamos el maximo
        if (!reservas.isEmpty()) {
            for (int i = 0; i < reservas.size(); i++) {
                if (Integer.parseInt(reservas.get(i).getIdReserva()) > maxIdReserva) {
                    maxIdReserva = Integer.parseInt(reservas.get(i).getIdReserva());
                }
            }
        }

        maxIdReserva++;

        try {
            Trayecto trayecto = this.trayectos.getTrayecto(origen, destino, hora);

            //Comprobamos que no este el tren lleno
            if (numReservasTrayecto(trayecto, hora) < trayecto.getTren().getNAsientos()) {
                Reserva res = new Reserva(String.valueOf(maxIdReserva), fecha, trayecto, hora, numAsiento);
                DBUtils.getDb().store(res);
                return res.getIdReserva();
            } else {
                throw new RuntimeException("El tren esta completo");
            }
        } catch (IllegalArgumentException ex) {
            throw ex;
        }
    }

    /**
     * Cancela una reserva existente. Se lleva a cabo tambien el borrado de dicha
     * reserva de la base de datos. Si la reserva no existe se lanzará
     * la determinada excepción.
     *
     * @param codigoReserva codigo de la reserva que queremos cancelar
     */
    @Override
    public void cancelaReserva(String codigoReserva) {
        final String idResAux = codigoReserva;

        List<Reserva> reservas = DBUtils.getDb().query(new Predicate<Reserva>() {

            public boolean match(Reserva r) {
                return r.getIdReserva().equals(idResAux);
            }
        });

        if (reservas.size() == 1) {
            Reserva encontrada = (Reserva) reservas.get(0);
            DBUtils.getDb().delete(encontrada);
        } else if (reservas.isEmpty()) {
            throw new IllegalArgumentException("Intentando borrar una reserva que no existe");
        } else {
            throw new RuntimeException("Reserva duplicada");
        }
    }

    /**
     * Devuele el precio del trayecto determinado por una ciudad origen, una ciudad
     * destino, una fecha de viaje, una hora de salida para realizar el trayecto.
     *
     * @param origen origen del trayecto
     * @param destino destino del trayecto
     * @param fecha fecha del viaje
     * @param hora hora del viaje
     * @return el precio del trayecto indicado
     */
    @Override
    public double getPrecio(String origen, String destino, LocalDate fecha, LocalTime hora) {
        try {
            return (double) (this.trayectos.getTrayecto(origen, destino, hora).getNTramos() * this.trayectos.getTrayecto(origen, destino, hora).getTren().getPrecioTramo());
        } catch (IllegalArgumentException ex) {
            throw ex;
        }
    }

    /**
     * Devuelve la lista de trayectos que tienen como ciudad origen y destino las
     * suministradas como parametro.
     *
     * @param origen: origen del trayecto.
     * @param destino: destino del trayecto.
     * @return lista con los trayectos que coinciden con el origen y el destino.
     */
    public List<Trayecto> getTrayectos(String origen, String destino) {
        return trayectos.get(origen, destino);
    }

    /**
     * Devuelve una lista con los trayectos que salen desde la cidad de origen.
     *
     * @param origen: origen del trayecto.
     * @return lista con los trayectos que coinciden con el origen.
     */
    public List<Trayecto> getTrayectosDesde(String origen) {
        return trayectos.getDesde(origen);
    }

    /**
     * Devuelve una lista con los trayectos que van hasta la cidad de destino.
     *
     * @param destino: destino del trayecto.
     * @return lista con los trayectos que coinciden con el destino.
     */
    public List<Trayecto> getTrayectosHasta(String destino) {
        return trayectos.getHasta(destino);
    }

    /**
     * Devuelve el numero que existe de asientos libres para un determinado viaje
     * dado su ciudad de origen, su ciudad destino, su fecha de realizacion, y la
     * hora de salida. En el caso de que alguno de los parámetros no sea correcto, debe de devolver
     * una excepcion IllegalArgumentException
     *
     * @param origen ciudad de origen
     * @param destino ciudad de destino
     * @param fecha fecha del viaje
     * @param hora hora del viaje
     * @return numero de asientos libres (no reservados)
     *
     */
    @Override
    public int asientosLibres(String origen, String destino, LocalDate fecha, LocalTime hora) {

        try {
            Trayecto t1 = trayectos.getTrayecto(origen, destino, hora);
            return t1.getTren().getNAsientos() - numReservasTrayecto(t1, fecha, hora);
        } catch (IllegalArgumentException ex) {
            throw ex;
        }
    }
}
